package com.ruiyian.netty.nettyclient;

import com.google.common.collect.Lists;
import com.ruiyian.common.constant.Constants;
import com.ruiyian.common.constant.HttpStatus;
import com.ruiyian.common.core.domain.AjaxResult;
import com.ruiyian.common.core.domain.entity.Point;
import com.ruiyian.common.core.redis.RedisCache;
import com.ruiyian.common.enums.ReturnType;
import com.ruiyian.common.thread.ThreadPoolUtil;
import com.ruiyian.common.utils.JSON;
import com.ruiyian.common.utils.jfinsUtil.AnalysisInfomationBoard;
import com.ruiyian.common.utils.jfinsUtil.FinsMessage;
import com.ruiyian.common.utils.jfinsUtil.FinsMessageUtils;
import com.ruiyian.common.utils.jfinsUtil.Hex;
import com.ruiyian.common.utils.spring.SpringUtils;
import com.ruiyian.common.vo.ReturnPoint;
import com.ruiyian.netty.nettyserver.MyChannelHandlerPool;
import com.ruiyian.project.equipment.service.EquipmentStatusService;
import com.ruiyian.project.log.domain.OperationLog;
import com.ruiyian.project.log.service.OperationLogService;
import com.ruiyian.system.domain.ServerEq;
import com.ruiyian.system.service.ServerService;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.ScheduledFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author chong
 * @email 673426164@qq.com
 * @create 2021/2/4
 * Desc: netty客户端业务操作
 */
@Slf4j
@ChannelHandler.Sharable
public class ClientHandler extends ChannelInboundHandlerAdapter {
    private static final String HEART_BEAT = "heart beat!";
    private int nowIndex = 0; // 指针索引
    // 操作日志
    private List<OperationLog> operationLogList = Lists.newArrayList();
    private OperationLog operationLog = null;
    // 注入日志操作类
    private static OperationLogService operationLogService;
    private static EquipmentStatusService equipmentStatusService;
    private static RedisCache redisCache;
    private List<Point> data = null;
    private List<Point> returnPointList = new ArrayList<>();
    // 在这个list里面的ip采用通过fins报文进行握手
    private List<String> finsiplist = new ArrayList<>();
    // modbus设备
    private List<String> modusiplist = new ArrayList<>();
    // 串口服务器
    private List<String> rxtxiplist = new ArrayList<>();

    static {
        // 命令头
        operationLogService = SpringUtils.getBean(OperationLogService.class);
        redisCache = SpringUtils.getBean(RedisCache.class);
        equipmentStatusService = SpringUtils.getBean(EquipmentStatusService.class);
    }

    /**
     * 初始化操作 获取对应参数
     */
    void setValue() {
        // 操作点位集合
        data = redisCache.getCacheList(Constants.POINTLIST);
    }

    /**
     * 客户端接收plc消息
     * 业务逻辑
     *
     * @param ctx
     * @param msg
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        try {
            InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
            int port = ipSocket.getPort();
            String host = ipSocket.getHostString();
            String serverpath = host + ":" + port;
            // modbus设备数据处理
            if(modusiplist.contains(serverpath)){
                log.info(ipSocket.getHostString() + "modbus接收到消息:{}", msg);
            }
            else if(rxtxiplist.contains(serverpath)){
                log.info(ipSocket.getHostString() + "rxtx接收到消息:{}", msg);
            }
            // fins设备的数据处理
            else if(finsiplist.contains(serverpath)){
                // 判断是否有fins数据报文头
                if (!msg.toString().contains(Hex.encodeHexStr(FinsMessage.FINS_HEADER_BYTE, false))) {
                    return;
                }
                log.info(ipSocket.getHostString() + "接收到消息:{}", msg);
                // 分析返回数据是什么类型的数据
                String messageType = FinsMessageUtils.analysisMessage(msg);
                if (StringUtils.isBlank(messageType)) {
                    return;
                }
                if (messageType.equals("handshakeMessage")) {
                    log.info("握手成功");
                    return;
                }
//            this.setValue();
                Map<String, ChannelFuture> connectMap = NettyStarter.getConnectMap();
                // 发送预案的代码
                FinsMessage finsMessage = null;
                Object key = redisCache.getCacheObject(Constants.NOWINDEX);
                nowIndex = Integer.parseInt(key.toString());
                String recoderserverpath = redisCache.getCacheObject("recoderserverpath"); // 上一次发送的plcip地址
                if (messageType.equals("readMessage")) {
                    // 获取上次操作的一些信息
                    String type =  "queryreturn";
                    Long tunnelId = redisCache.getCacheObject("tunnelId");
                    Long moduleId = redisCache.getCacheObject("moduleId");
                    Long memoryArea = redisCache.getCacheObject("memoryArea");
                    Long registerAddress = redisCache.getCacheObject("registerAddress");
                    String queryresultstr = msg.toString();
                    // 读取返回数据字典
                    Map<String, Object> queryresultmap = new HashMap<>();
                    queryresultmap.put("type", type);
                    queryresultmap.put("tunnelId", tunnelId);
                    queryresultmap.put("moduleId", moduleId);
                    queryresultmap.put("memoryArea", memoryArea);
                    queryresultmap.put("registerAddress", registerAddress);
                    queryresultmap.put("value", queryresultstr.substring(queryresultstr.length()-1));
                    // 判断是否是正在连接的设备,发回数据给往网页
                    if(recoderserverpath.equals(host+":"+port)){
                        MyChannelHandlerPool.channelGroup.writeAndFlush(new TextWebSocketFrame(JSON.stringify(queryresultmap)));
                        redisCache.deleteObject("recoderserverpath");
                    }
                }
                if (messageType.equals("writeMessage")) {
                    // 从缓存中获取上次操作信息
                    String type =  "writereturn";
                    Long tunnelId = redisCache.getCacheObject("tunnelId");
                    Long moduleId = redisCache.getCacheObject("moduleId");
                    Long memoryArea = redisCache.getCacheObject("memoryArea");
                    Long registerAddress = redisCache.getCacheObject("registerAddress");
                    // 创建返回数组
                    Map<String, Object> writeresultmap = new HashMap<>();
                    writeresultmap.put("type", type);
                    writeresultmap.put("tunnelId", tunnelId);
                    writeresultmap.put("moduleId", moduleId);
                    writeresultmap.put("memoryArea", memoryArea);
                    writeresultmap.put("registerAddress", registerAddress);
                    writeresultmap.put(AjaxResult.CODE_TAG, HttpStatus.SUCCESS);
                    // 判断是否是正在连接的设备,发回数据给往网页
                    if(recoderserverpath.equals(host+":"+port)){
                        MyChannelHandlerPool.channelGroup.writeAndFlush(new TextWebSocketFrame(JSON.stringify(writeresultmap)));
                    }
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    // 创建日志list
    void createOperaLog(String equipType, String equipName, String command, String sendStatus, String type, String controlGroup, Integer executeSeq, String operation, String pileNum, String value, Long eqId, Long tunnelId) {
        operationLog = new OperationLog(equipType, equipName, null, command, sendStatus, type, controlGroup, executeSeq, operation, pileNum, value, eqId, tunnelId);
        operationLog.setSendTime(new Date());
        operationLogList.add(operationLog);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        ctx.close();
    }
    /**
     * 关闭连接
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        int port = ipSocket.getPort();
        String host = ipSocket.getHostString();
        log.error("客户端与设备" + host + ":" + port + "连接断开!");
        NettyStarter.setConnectRemove(host, port);
        ctx.channel().closeFuture().sync();
        ctx.close();
        final EventLoop eventLoop = ctx.channel().eventLoop();
        eventLoop.schedule(new Runnable() {
            @Override
            public void run() {
                NettyStarter.doConnect(host, port);
            }
        }, 5L, TimeUnit.SECONDS);
        super.channelInactive(ctx);
    }

    /**
     * 空闲心跳机制
     */
  /*  @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleState state = ((IdleStateEvent) evt).state();
            if (state == IdleState.WRITER_IDLE) {// 写空闲时发心跳
                System.out.println("发送心跳");
                //log.info("发送心跳");
                ctx.writeAndFlush(HEART_BEAT + "\n");
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }*/

    /**
     * 建立连接
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        int port = ipSocket.getPort();
        String host = ipSocket.getHostString();
        String serverpath = host + ":" + port;
        // 得到获取服务列表的bean
        ServerService serverService = SpringUtils.getBean(ServerService.class);
        List<ServerEq> allserverlist = serverService.selectServerByTunnelId((long) 0, 0);
        for(ServerEq onesrver:allserverlist){
            if(onesrver.getCode().equals("plc")){
                finsiplist.add(onesrver.getHost()+":"+onesrver.getPort());
            }
            else if(onesrver.getCode().equals("modbus")){
                modusiplist.add(onesrver.getHost()+":"+onesrver.getPort());
            }
            else if(onesrver.getCode().equals("rxtx")){
                rxtxiplist.add(onesrver.getHost()+":"+onesrver.getPort());
            }
        }
        // 在plc列表中，下发fins握手指令
        if(finsiplist.contains(serverpath)){
            log.info("客户端与fins-plc：" + host + ":" + port + "连接成功!");
            // 握手连接  udp 6e = 110（传入本机ip，最后的数）
            FinsMessage finsMessage = new FinsMessage(Constants.DEFAULT_SA1);
            // 通道建立后，立马下发握手指令
            ctx.writeAndFlush(finsMessage.toString());
            NettyStarter.setConnect(host, port, ctx.channel().newSucceededFuture());
            super.channelActive(ctx);
//        ReferenceCountUtil.release(finsMessage);
//        定时心跳机制
//        this.channel = ctx.channel();
//        ping(ctx.channel());
        }
        else if(modusiplist.contains(host)){
            log.info("客户端与modbus设备：" + host + ":" + port + "连接成功!");
        }
        else if(rxtxiplist.contains(host)){
            log.info("客户端与串口服务器设备：" + host + ":" + port + "连接成功!");
        }
    }

   /* private void ping(Channel channel) {
        ScheduledFuture<?> future = channel.eventLoop().schedule(new Runnable() {
            @Override
            public void run() {
                if (channel.isActive()) {
                    channel.writeAndFlush(HEART_BEAT);
                } else {
                    channel.closeFuture();
                    throw new RuntimeException();
                }
            }
        }, 10L, TimeUnit.SECONDS);
        future.addListener(new GenericFutureListener() {
            @Override
            public void operationComplete(Future future) throws Exception {
                if (future.isSuccess()) {
                    ping(channel);
                }
            }
        });
    }*/
}
